﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Flower.Directory.Scripting;
using Flower.Services.Data;
using Jurassic;
using Jurassic.Library;
using System.Transactions;
using System.Reflection;

namespace Flower.Directory.Management.Scripting
{
    public static class Setup
    {
        private const string SCRIPTS_NAMESPACE = "Flower.Directory.Management.Scripting.Scripts";
        private static readonly TransactionOptions TRANSACTION_OPTIONS = 
            new TransactionOptions
            {
                IsolationLevel = IsolationLevel.ReadCommitted,
                Timeout = TimeSpan.FromSeconds(30)
            };

        private static readonly string Scripts;
        
        static Setup()
        {
            System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
            string[] resourceNames = assembly.GetManifestResourceNames();

            var scripts = new StringBuilder();

            foreach (string resourceName in resourceNames)
            {
                if (resourceName.StartsWith(SCRIPTS_NAMESPACE))
                {
                    var stream = assembly.GetManifestResourceStream(resourceName);

                    if (stream != null)
                    {
                        using (var reader = new StreamReader(stream, Encoding.UTF8))
                        {
                            scripts.Append(reader.ReadToEnd());
                        }
                    }
                }
            }

            Scripts = scripts.ToString();
        }

        public static ScriptEngine InitEngine(ScriptEngine engine)
        {
            #region Printing

            // Writes an object to stdout in a human readable format.
            //
            // obj - the object to write.
            engine.SetGlobalFunction
            (
                "print", 
                new Action<object>
                (
                    obj => 
                    {
                        try
                        {
                            Printer.Print(Console.Out, obj, false);
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );

            // Writes an object to stdout in a human readable format appending the new line char to the end.
            //
            // obj - the object to write.
            engine.SetGlobalFunction
            (
                "println",
                new Action<object>
                (
                    obj =>
                    {
                        try
                        {
                            Printer.Print(Console.Out, obj, true);
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );

            #endregion

            #region Directory import\export

            engine.SetGlobalFunction
            (
                "importFromFileSystem",
                new Action<string>
                (
                    path =>
                    {
                        try
                        {
                            var dirObj = engine.GetGlobalValue<ObjectInstance>("$dir");
                            var dirObjTyped = dirObj as DirectoryObject;

                            if (dirObjTyped != null)
                            {
                                dirObjTyped.Directory.ImportFromFileSystem(path);
                            }
                            else
                            {
                                throw new InvalidOperationException("Directory object doesn't exist.");
                            }
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );
            engine.SetGlobalFunction
            (
                "importFromArchive",
                new Action<string>
                (
                    path =>
                    {
                        try
                        {
                            var dirObj = engine.GetGlobalValue<ObjectInstance>("$dir");
                            var dirObjTyped = dirObj as DirectoryObject;

                            if (dirObjTyped != null)
                            {
                                dirObjTyped.Directory.ImportFromArchive(path);
                            }
                            else
                            {
                                throw new InvalidOperationException("Directory object doesn't exist.");
                            }
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );
            engine.SetGlobalFunction
            (
                "exportToFileSystem",
                new Action<string, int>
                (
                    (path, directoryEntryTypes) =>
                    {
                        try
                        {
                            var dirObj = engine.GetGlobalValue<ObjectInstance>("$dir");
                            var dirObjTyped = dirObj as DirectoryObject;

                            if (dirObjTyped != null)
                            {
                                dirObjTyped.Directory.ExportToFileSystem(path, (DirectoryEntryTypes)directoryEntryTypes);
                            }
                            else
                            {
                                throw new InvalidOperationException("Directory object doesn't exist.");
                            }
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );
            engine.SetGlobalFunction
            (
                "exportToArchive",
                new Action<string, int>
                (
                    (path, directoryEntryTypes) =>
                    {
                        try
                        {
                            var dirObj = engine.GetGlobalValue<ObjectInstance>("$dir");
                            var dirObjTyped = dirObj as DirectoryObject;

                            if (dirObjTyped != null)
                            {
                                dirObjTyped.Directory.ExportToArchive(path, (DirectoryEntryTypes)directoryEntryTypes);
                            }
                            else
                            {
                                throw new InvalidOperationException("Directory object doesn't exist.");
                            }
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );

            #endregion

            #region Assemblies importing

            engine.SetGlobalFunction
            (
                "importAssemblies",
                new Action<object>
                (
                    pathsObj =>
                    {
                        try
                        {
                            var pathsArr = pathsObj as ArrayInstance;
                            if (pathsArr == null)
                            {
                                throw new InvalidOperationException("Argument must be an array of paths.");
                            }

                            var paths = new string[pathsArr.Length];

                            for (int i = 0; i < pathsArr.Length; i++)
                            {
                                object p = pathsArr[i];

                                if (p == null || (p is Null) || (p is Undefined))
                                {
                                    throw new InvalidOperationException("Argument must be an array of paths.");
                                }

                                paths[i] = p.ToString();
                            }

                            var dirObj = engine.GetGlobalValue<ObjectInstance>("$dir");
                            var dirObjTyped = dirObj as DirectoryObject;

                            if (dirObjTyped != null)
                            {
                                dirObjTyped.Directory.ImportAssemblies(paths);
                            }
                            else
                            {
                                throw new InvalidOperationException("Directory object doesn't exist.");
                            }
                        }
                        catch (ReflectionTypeLoadException ex)
                        {
                            string loaderExceptions = string.Empty;
                            if (ex.LoaderExceptions != null)
                            {
                                loaderExceptions =
                                    string.Join(" --> ", ex.LoaderExceptions.Select(e => e.Message));
                            }

                            throw new JavaScriptException(engine, "Error", string.Concat(ex.Message, ", ", loaderExceptions) , ex);
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );

            #endregion

            #region Transactions

            engine.SetGlobalFunction
            (
                "transaction",
                new Action<FunctionInstance>
                (
                    f => 
                    {
                        try
                        {
                            using (var trn = new TransactionScope(TransactionScopeOption.Required, TRANSACTION_OPTIONS))
                            {
                                f.Call(Null.Value);
                                trn.Complete();
                            }
                        }
                        catch (Exception ex)
                        {
                            if (ex is JavaScriptException)
                            {
                                throw;
                            }

                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );

            #endregion

            #region Find

            engine.SetGlobalFunction
            (
                "find",
                new Func<object, object, object>
                (
                    (what, entryTypes) =>
                    {
                        try
                        {
                            var dirObj = engine.GetGlobalValue<ObjectInstance>("$dir");
                            var dirObjTyped = dirObj as DirectoryObject;

                            if (dirObjTyped != null)
                            {
                                if (what == null || (what is Null) || (what is Undefined))
                                {
                                    throw new InvalidOperationException("Argument 'what' must be specified.");
                                }

                                var entryTypesNum = entryTypes as NumberInstance;

                                if (entryTypesNum == null)
                                {
                                    throw new InvalidOperationException("Argument 'entryTypes' must be specified as a single or concatenation of the DET_ flags.");
                                }

                                ArrayInstance arr = dirObjTyped.Engine.Array.New();

                                var found =
                                    dirObjTyped
                                        .Directory
                                        .Find(what.ToString(), (DirectoryEntryTypes)(int)entryTypesNum.Value)
                                        .Select(ent => JsConvert.EntryToObject(dirObjTyped.Engine, ent));

                                foreach (DirectoryEntryObject ent in found)
                                {
                                    arr.Push((object)ent);
                                }

                                return arr;
                            }

                            throw new InvalidOperationException("Directory object doesn't exist.");
                        }
                        catch (Exception ex)
                        {
                            throw new JavaScriptException(engine, "Error", ex.Message, ex);
                        }
                    }
                )
            );

            #endregion

            engine.SetGlobalValue("$fs", new FileSystemObject(engine));

            engine.SetGlobalValue("dump", new Dump(engine));
            engine.SetGlobalValue("processor", new ProcessorObjectConstructor(engine));
            engine.SetGlobalValue("queue", new QueueObjectConstructor(engine));
            engine.SetGlobalValue("__jsonToSpringContainer", new JsonToSpringContainer(engine));
            engine.SetGlobalValue("decommissionProcessor", new DecommissionProcessor(engine));

            engine.Execute(Scripts);

            return engine;
        }
    }
}
